module pflanza.tesselator;

import derelict.opengl.glu;
import derelict.opengl.gltypes;
import objects.triangle;
import utils.vector3;

alias Vector3!(double) Vector3d;

enum GluTessMethod {
    triangles,
    triangleStrip,
    triangleFan,
}

public class Tesselator {
    private {
        GLUtesselator* mGluTess;
        GluTessMethod mCurTess;
        Vector3d[] mVertd;
        Vector3f[] mVertf;
        Triangle[] mTris;
        int mTriIdx, mCount;
        int[3] mTriCache;
    }

    this() {
        DerelictGLU.load();
        mGluTess = gluNewTess();
        assert(mGluTess !is null);
        gluTessCallback(mGluTess,GLU_TESS_BEGIN_DATA,cast(_GLUfuncptr)&beginData);
        gluTessCallback(mGluTess,GLU_TESS_END_DATA,cast(_GLUfuncptr)&endData);
        gluTessCallback(mGluTess,GLU_TESS_VERTEX_DATA,cast(_GLUfuncptr)&vertexData);
        gluTessProperty(mGluTess,GLU_TESS_BOUNDARY_ONLY,GL_FALSE);
    }

    public Triangle[] tesselate(Vector3f[] points) {
        beginPolygon();

        for (int i = 0; i < points.length; i++) {
            addVertex(points[i]);
        }

        return endPolygon();
    }

    public void beginPolygon() {
        gluTessBeginPolygon(mGluTess,cast(void*)this);
        gluTessBeginContour(mGluTess);

        mVertd.length = 0;
        mVertf.length = 0;
    }

    public void addVertex(Vector3f v) {
        mVertf ~= v;
        Vector3d vd;
        vd.x1 = v.x1;
        vd.x2 = v.x2;
        vd.x3 = v.x3;
        mVertd ~= vd;

        gluTessVertex(mGluTess,&mVertd[$-1].x1,cast(void*)(mVertd.length-1));
    }

    public Triangle[] endPolygon() {
        mTris.length = 0;

        gluTessEndContour(mGluTess);
        gluTessEndPolygon(mGluTess);

        return mTris;
    }

    protected void gluBegin(GluTessMethod m) {
        //std.stdio.writefln("vertex begin: %s",m);
        mCurTess = m;
        mCount = 0;
        mTriIdx = 0;
    }

    protected void gluVertex(int idx) {
        mCount++;
        mTriCache[mTriIdx] = idx;
        switch (mCurTess) {
            case GluTessMethod.triangles:
                if (mTriIdx == 2) {
                    Triangle t = Triangle([mVertf[mTriCache[0]],mVertf[mTriCache[1]],mVertf[mTriCache[2]]]);
                    mTris ~= t;
                }
                mTriIdx = (mTriIdx+1) % 3;
                break;
            case GluTessMethod.triangleStrip:
                if (mCount > 2) {
                    Triangle t;
                    if (mCount % 2 == 1)
                        t = Triangle([mVertf[mTriCache[0]],mVertf[mTriCache[1]],mVertf[mTriCache[2]]]);
                    else
                        t = Triangle([mVertf[mTriCache[0]],mVertf[mTriCache[2]],mVertf[mTriCache[1]]]);
                    mTris ~= t;
                }
                mTriIdx = (mTriIdx+1) % 3;
                break;
            case GluTessMethod.triangleFan:
                if (mCount > 2) {
                    Triangle t;
                    if (mCount % 2 == 1)
                        t = Triangle([mVertf[mTriCache[0]],mVertf[mTriCache[1]],mVertf[mTriCache[2]]]);
                    else
                        t = Triangle([mVertf[mTriCache[0]],mVertf[mTriCache[2]],mVertf[mTriCache[1]]]);
                    mTris ~= t;
                }
                mTriIdx = mTriIdx % 2 + 1;
                break;
        }
    }

    protected void gluEnd() {
        //std.stdio.writefln("vertex end");
    }
}

extern(Windows) {
    void  beginData(GLenum type, void* polygon_data) {
        GluTessMethod m;
        switch (type) {
            case GL_TRIANGLES: m = GluTessMethod.triangles; break;
            case GL_TRIANGLE_STRIP: m = GluTessMethod.triangleStrip; break;
            case GL_TRIANGLE_FAN: m = GluTessMethod.triangleFan; break;
        }
        (cast(Tesselator)polygon_data).gluBegin(m);
    }

    void vertexData(void* vertex_data, void* polygon_data) {
        (cast(Tesselator)polygon_data).gluVertex(cast(int)vertex_data);
    }

    void endData(void* polygon_data) {
        (cast(Tesselator)polygon_data).gluEnd();
    }
}
